#include <arch.h>
#include <assert.h>
#include <conio.h>
#include <list.h>
#include <queue.h>
#include <schedule.h>

#include <sys/kmalloc.h>
#include <sys/thread.h>
#include <sys/task.h>
#include <sys/unistd.h>

extern thread_t *curth;
/* queue of running threads */
static queue_t *runq;
/* queue of wakeup threads */
static queue_t *wakeq;
/* todo: adding prio!!! */
static int maxprio = 0x20;

static void runq_remove(thread_t *th);
static void runq_enqueue(thread_t *th);
static void runq_insert(thread_t *th);
static thread_t *runq_dequeue(void);
static void sched_switch(void);
static void wakeq_flush(void);
static void sched_setrun(thread_t *th);

/*
 * sched_tick() is called from timer_scheduler() once every tick.
 * Check quantum expiration, and mark a rescheduling flag.
 * We don't need locking in here.
 */
void sched_tick(void)
{
    if (curth->state != TH_EXIT) {
	/*
	 * Bill time to current thread.
	 */
	curth->run_time++;

	if (curth->sched_type == SCHED_RR) {
	    if (--curth->time_left <= 0) {
		/*
		* The quantum is up.
		* Give the thread another.
		*/
		curth->time_left += QUANTUM;
		curth->resched = 1;
	    }
	}
    }
}

/*
 * stop scheduling of the specified thread.
 */
void sched_stop(thread_t *th)
{
    if (th == curth) {
	/*
	 * If specified thread is a current thread,
	 * the scheduling lock count is force set
	 * to 1 to ensure the thread switching in
	 * the next sched_unlock().
	 */
	curth->sched_lock = 1;
	curth->resched = 1;
    } else {
	if (th->state == TH_RUN)
	    runq_remove(th);
	else if (th->state & TH_SLEEP)
	    queue_remove(&th->sched_link);
    }

    //timer_stop(&t->timeout);
    th->state = TH_EXIT;
}

/* main func of scheduler */
context_t *schedule(void) 
{
    sched_lock();
    sched_tick();	
    sched_unlock();

    /* todo: needed moving code to arch! */
    if (!new_cr3)	/* if zero then old pdir value */
        new_cr3 = old_cr3;
    //kprintf("old_cr3 0x%lx, new_cr3 0x%lx\n", old_cr3, new_cr3);

    return ((context_t *)&curth->ctx);
}

void sched_suspend(thread_t *th)
{
    sched_lock();

    if (th->state == TH_RUN) {
	if (th == curth) {
	    curth->resched = 1;
	} else {
	    runq_remove(th);
	}
    }

    sched_unlock();
}

/*
 * Resume the specified thread.
 * Called with scheduler locked.
 */
void sched_resume(thread_t *th)
{
    sched_lock();
    
    if (th->state & TH_SUSPEND) {
	th->state &= ~TH_SUSPEND;
	if (th->state == TH_RUN)
	    runq_enqueue(th);
	}
    
    sched_unlock();
}

/*
 * sched_unsleep - cancel sleep.
 *
 * sched_unsleep() removes the specified thread from its
 * sleep queue. The specified sleep result will be passed
 * to the sleeping thread as a return value of sched_tsleep().
 */
void sched_unsleep(thread_t *th, int result)
{
    int s;

    sched_lock();
    if (th->state & TH_SLEEP) {
	s = splhi();
	queue_remove(&th->sched_link);
	th->sleep_rc = result;
	sched_setrun(th);
	splx(s);
    }
    sched_unlock();
}

/*
 * Yield the current processor to another thread.
 *
 * Note that the current thread may run immediately again,
 * if no other thread exists in the same priority queue.
 */
void sched_yield(void)
{
    sched_lock();

    if (!queue_empty(runq))
	curth->resched = 1;

    sched_unlock();		/* Switch a current thread here */
}

static void runq_remove(thread_t *th)
{
    if (th == NULL)
	return;

    queue_remove(&th->sched_link);
}

static void runq_enqueue(thread_t *th)
{
    if (th == NULL)
	return;

    enqueue(runq, &th->sched_link);
}

/*
 * Pick up and remove the highest-priority thread
 * from the run queue.
 */
static thread_t *runq_dequeue(void)
{
    queue_t *q;
    thread_t *th;

    q = dequeue(runq);
    th = queue_entry(q, thread_t, sched_link);
    if (queue_empty(runq))
		maxprio = 0x20;		/* searching for best prio */

    return (th);
}

/*
 * Insert a thread to the head of the run queue.
 * We assume this routine is called while thread switching.
 */
static void runq_insert(thread_t *th)
{
    queue_insert(runq, &th->sched_link);
    if (th->prio < maxprio)
		maxprio = th->prio;
}

/*
 * Setup the thread structure to start scheduling.
 */
void sched_start(thread_t *th, int prio, int policy)
{
    th->state = TH_RUN | TH_SUSPEND;
    th->sched_type = policy;
    th->prio = prio;
    th->base_prio = prio;
    
    if (th->sched_type == SCHED_RR)
		th->time_left = QUANTUM;
}

void sched_lock(void)
{
    assert(curth != NULL);	/* checking for NULL */
    
    curth->sched_lock++;
}

void sched_wakeup(event_t *evt) 
{
    thread_t *th;
    queue_t *q;
    queue_t *wakeq;
    int s;

    wakeq = &evt->waitq;
    assert(wakeq != NULL);

    sched_lock();
    //s = splhigh();
    while (!queue_empty(wakeq)) {
        q = dequeue(wakeq);
	th = queue_entry(q, thread_t, sched_link);
	th->sleep_rc = 0;
	sched_setrun(th);
    };
    
    //splx(s);
    sched_unlock();
}

/*
 * Wake up all threads in the wake queue.
 */
static void wakeq_flush(void)
{
    queue_t *q;
    thread_t *th;

    while (!queue_empty(wakeq)) {
	/*
	 * Set a thread runnable.
	 */
	q = dequeue(wakeq);
	th = queue_entry(q, thread_t, sched_link);
	th->state &= ~TH_SLEEP;
	kprintf("found thread 0x%lx\n", th);
	if (th != curth && th->state == TH_RUN)
	    runq_enqueue(th);
    };
}

static void sched_setrun(thread_t *th)
{
    enqueue(wakeq, &th->sched_link);
    //timer_stop(&t->timeout);
}

/*
 * sched_switch - this is the scheduler proper:
 *
 * If the scheduling reason is preemption, the current thread
 * will remain at the head of the run queue.  So, the thread
 * still has right to run next again among the same priority
 * threads. For other scheduling reason, the current thread is
 * inserted into the tail of the run queue.
 */
static void sched_switch(void)
{
    thread_t *prev, *next;

    /*
     * Put the current thread on the run queue.
     */
    prev = curth;
    if (prev->state == TH_RUN) {
	if (prev->prio > maxprio)
	    runq_insert(prev);	/* preemption */
	else
	    runq_enqueue(prev);
    }
    prev->resched = 0;

    /*
     * Select the thread to run the CPU next.
     * If it's same with previous one, return.
     */
    next = runq_dequeue();
    if (next == prev)
		return;
    	curth = next;

    /*
     * Switch to the new thread.
     * You are expected to understand this..
     */

    /* switching in schedule!!! */
    if (prev->owner != next->owner) {
		new_cr3 = next->owner->vmap->pdir;
		kprintf("sched_switch(): new cr3 0x%lx\n", new_cr3);
    }


    //	vm_switch(next->task->map);
    //context_switch(&prev->ctx, &next->ctx);
}

/*
 * sched_unlock - unlock scheduler.
 *
 * If nobody locks the scheduler anymore, it checks the
 * rescheduling flag and kick the scheduler if it's required.
 * This routine will be always called at the end of each
 * interrupt handler.
 */
void sched_unlock(void)
{
    //int s;

    assert(curth->sched_lock > 0);

    //s = splhigh();
    if (curth->sched_lock == 1) {
	wakeq_flush();
	while (curth->resched) {
	    /*
	     * kick up scheduler.
	     */
	    sched_switch();

	    /*
	     * Now we run pending interrupts which fired
	     * during the thread switch. So, we can catch
	     * the rescheduling request from such ISRs.
	     * Otherwise, the reschedule may be deferred
	     * until _next_ sched_unlock() call.
	     */
	    //splx(s);
	    //s = splhigh();
	    wakeq_flush();
	};
    }
    
    curth->sched_lock--;
    //splx(s);
}

/*
 * sched_tsleep - sleep the current thread until a wakeup
 * is performed on the specified event.
 * This routine returns a sleep result.
 */
 /* todo: change event ptr from queue_t to another */
int sched_thsleep(event_t *evt, uint32_t msec)
{
    int s;

    assert(evt != NULL);

    sched_lock();
    //s = splhigh();

    /*
     * put the current thread on the sleep queue.
    */
    curth->sleep_evt = evt;
    curth->state |= TH_SLEEP;
    enqueue(&evt->waitq, &curth->sched_link);

	/*
	 * program timer to wake us up at timeout.
	 * todo: adding timers!
	 */
/*	if (msec != 0) {
		timer_callout(&curthread->timeout, msec,
			      &sleep_timeout, curthread);
	}*/


    wakeq_flush();
    sched_switch();		/* Sleep here. Zzzz.. */

//	splx(s);
    sched_unlock();

    return (curth->sleep_rc);
}

thread_t *sched_wakeupone(event_t *evt)
{
    queue_t *head, *q;
    thread_t *top, *th = NULL;
    int s;

    if (!evt)
        return NULL;
        
    sched_lock();
    //s = splhigh();
    head = &evt->waitq;
    if (!queue_empty(head)) {
	/*
	 * Select the highet priority thread in
	 * the sleep queue, and wake it up.
	 */
	q = queue_first(head);
	top = queue_entry(q, thread_t, sched_link);
	while(!queue_end(head, q)) {
	    th = queue_entry(q, thread_t, sched_link);
	    if (th->prio < top->prio)
	        top = th;
		q = queue_next(q);
	};

	queue_remove(&top->sched_link);
	top->sleep_rc = 0;
	sched_setrun(top);
    }
    
    //splx(s);
    sched_unlock();

    return (th);
}

void sched_init(void)
{
    kprintf("init scheduler...");

    runq = (queue_t *)kmalloc(sizeof(queue_t), GFP_KERNEL);
    assert(runq != NULL);
    bzero(runq, sizeof(queue_t));
    
    wakeq = (queue_t *)kmalloc(sizeof(queue_t), GFP_KERNEL);
    assert(wakeq != NULL);
    bzero(wakeq, sizeof(queue_t));
    
    queue_init(runq);
    queue_init(wakeq);

    kprintf("Done.\n");
}

